﻿using System;
using System.Linq;
using System.Collections.Generic;
using System.Web.Mvc;
using HashFoo.Rest.Common;

namespace HashFoo.Rest.Server
{
    /// <summary>
    /// Applies <see cref="IHaveQueryMeta.QueryMeta"/> to a <see cref="ResourceActionResult"/>.
    /// </summary>
    /// <remarks>
    /// You can decorate your action methods with <see cref="IgnoreQueryMetaAttribute"/> to have this filter not automagically execute.
    /// </remarks>
    public class QueryMetaExecutorFilter : IActionFilter
    {
        const string ResourceQueryDataKey = "resource_query";

        /// <summary>
        /// Called before an action method executes.
        /// </summary>
        /// <param name="filterContext">The filter context.</param>
        public void OnActionExecuting(ActionExecutingContext filterContext)
        {
            var ignoreMeta = filterContext.ActionDescriptor.GetCustomAttributes(true).FirstOrDefault(t => typeof(IgnoreQueryMetaAttribute) == t.GetType());
            if (ignoreMeta != null) return;

            var query =
                filterContext.ActionParameters
                    .Where(p => p.Value != null && p.Value.GetType().GetInterfaces().Any(t => t == typeof(IHaveQueryMeta)))
                    .Select(p => p.Value)
                    .Cast<IHaveQueryMeta>()
                    .FirstOrDefault();

            if (query == null) return;

            // we have a resource query, and we haven't been told to ignore it.
            // cache it so that we can apply the query meta after the action is executed.
            filterContext.Controller.TempData[ResourceQueryDataKey] = query;
        }

        /// <summary>
        /// Called after the action method executes.
        /// </summary>
        /// <remarks>
        /// Depends on <see cref="OnActionExecuting"/> setting the actual resource query object.
        /// </remarks>
        /// <param name="filterContext">The filter context.</param>
        public void OnActionExecuted(ActionExecutedContext filterContext)
        {
            // we only execute query meta data on a ResourceActionResult that's resource is an IQueryable.
            var resourceResult = filterContext.Result as ResourceActionResult;
            if (resourceResult == null) return;

            var data = resourceResult.Resource as IQueryable;
            if (data == null) return;

            // we should have the orignal resource query stored.  If not ignore.
            if (filterContext.Controller.TempData.ContainsKey(ResourceQueryDataKey) == false ||
                filterContext.Controller.TempData[ResourceQueryDataKey] == null) return;

            var query = filterContext.Controller.TempData[ResourceQueryDataKey] as IHaveQueryMeta;
            if (query == null) throw new InvalidOperationException("Programmer Error: this object should alwasy be an IHaveQueryMeta at this point");

            // if the resource query doesn't actually have meta data, ignore.
            if (query.HasQueryMeta == false) return;

            // apply the meta data to the IQueryable resource, and reset the result.
            filterContext.Result = new ResourceActionResult(query.QueryMeta.ApplyTo(data));
        }
    }
}